/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.deservel.wxopen.common.utils;

import com.deservel.wxopen.model.message.response.BaseResponse;
import com.deservel.wxopen.model.message.response.NewsResponse;
import com.deservel.wxopen.common.Annotation.XStreamCDATA;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * XML工具类
 *
 * @author DeserveL
 * @error 如果字段名字相同有bug
 * @date 2017/7/3 22:33
 * @since 1.0.0
 */
public class XmlUtils {

    private static String PREFIX_CDATA = "<![CDATA[";
    private static String SUFFIX_CDATA = "]]>";

    public static Map<String, String> fromXmlToMap(String xmlStr) throws DocumentException {
        //将解析结果存储在HashMap 中
        Map<String, String> rsMap = new HashMap<>();

        Document document = DocumentHelper.parseText(xmlStr);
        //得到xml 根元素
        Element root = document.getRootElement();
        //得到根元素的所有子节点
        List<Element> elementList = root.elements();

        //遍历所有子节点
        for (Element e : elementList) {
            rsMap.put(e.getName(), e.getText());
        }
        return rsMap;
    }

    public static <T> T fromXmlToBean(String xmlStr, Class<T> type) {
        XStream xStream = new XStream();
        //通过注解方式的
        xStream.processAnnotations(type);
        //略掉未知的XML要素
        xStream.ignoreUnknownElements();
        return (T) xStream.fromXML(xmlStr);
    }

    public static <T> T fromXmlToBeanWithOutAnnotations(String xmlStr, Class<T> type) {
        XStream xStream = new XStream();
        xStream.alias("xml", type);
        //略掉未知的XML要素
        xStream.ignoreUnknownElements();
        return (T) xStream.fromXML(xmlStr);
    }

    public static <T> String fromModelToXml(T t) {
        XStream xStream = createXstream();
        //通过注解方式的
        xStream.processAnnotations(t.getClass());
        return xStream.toXML(t);
    }

    /**
     * 重写Xstream，使其支持Cdata注解
     *
     * @return
     */
    public static XStream createXstream() {
        return new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    boolean cdata = false;
                    Class<?> targetClass = null;

                    @Override
                    public void startNode(String name,
                                          @SuppressWarnings("rawtypes") Class clazz) {
                        super.startNode(name, clazz);
                        //业务处理，对于用XStreamCDATA标记的Field，需要加上CDATA标签
                        if (!name.equals("xml")) {
                            cdata = needCDATA(targetClass, name);
                        } else {
                            targetClass = clazz;
                        }
                    }

                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (cdata) {
                            writer.write(PREFIX_CDATA);
                            writer.write(text);
                            writer.write(SUFFIX_CDATA);
                        } else {
                            writer.write(text);
                        }
                    }
                };
            }
        });
    }

    /**
     * 查看当前类和其父类的属性是否包含XStreamCDATA注解
     *
     * @param targetClass
     * @param fieldAlias
     * @return
     */
    private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
        //查看是否含有此字段注解(包括父类)
        Class<?> tempClass = targetClass;
        while (tempClass != null && !Object.class.equals(tempClass)) {
            if (existsCDATA(tempClass, fieldAlias)) {
                return true;
            }
            tempClass = tempClass.getSuperclass();
        }
        return false;
    }

    /**
     * 查看当前类的属性是否包含cdata注解
     *
     * @param clazz
     * @param fieldAlias
     * @return
     */
    private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
        //获取属性
        if (null != clazz) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //获取属性类型
                Class fieldClazz = getParameterizedType(field);
                //特殊类型
                if (fieldClazz != null) {
                    if (existsCDATA(fieldClazz, fieldAlias)) {
                        return true;
                    }
                    //基本类型或者基本类型的包装类型
                } else {
                    if (fieldWithCDATA(field, fieldAlias)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 查看属性是否包含cdata注解（和XStreamAlias也做对比）
     *
     * @param field
     * @param fieldAlias
     * @return
     */
    private static boolean fieldWithCDATA(Field field, String fieldAlias) {
        //1. 首先判断是否含有XStreamCDATA
        if (field.getAnnotation(XStreamCDATA.class) != null) {
            XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
            //2. 判断是否含有XStreamAlias注解
            if (null != xStreamAlias) {
                //若有XStreamAlias注解，则和其注解值对比
                if (fieldAlias.equals(xStreamAlias.value())) {
                    return true;
                }
            } else {
                //没有XStreamAlias注解，直接和属性值对比
                if (fieldAlias.equals(field.getName())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 返回属性的特殊类型
     *
     * @param field
     * @return
     */
    public static Class getParameterizedType(Field field) {
        Class fieldClazz = field.getType(); // 得到field的class及类型全路径
        //判断是否为基本类型
        if (fieldClazz.isPrimitive()) {
            return null;
        }
        //基本类型的包装类型
        if (fieldClazz.getName().startsWith("java.lang")) {
            return null;
        }
        //List类型
        if (fieldClazz.isAssignableFrom(List.class)) {
            //如果是List类型，得到其Generic的类型
            Type fc = field.getGenericType();
            //如果是泛型参数的类型
            if (fc instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) fc;
                Class genericClazz = (Class) pt.getActualTypeArguments()[0];
                return genericClazz;
            }
        } else {
            return field.getType();
        }
        return null;
    }

    /**
     * 判断此类型是否为基本类型或者基本类型的包装类型或者String
     *
     * @param clz
     * @return
     */
    public static boolean isWrapClass(Class clz) {
        try {
            if (!String.class.equals(clz)) {
                if (!clz.isPrimitive()) {
                    return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
                } else {
                    return true;
                }
            } else {
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        String xml = "<xml>\n" +
                "<ToUserName><![CDATA[toUser]]></ToUserName>\n" +
                "<FromUserName><![CDATA[fromUser]]></FromUserName>\n" +
                "<CreateTime>12345678</CreateTime>\n" +
                "<MsgType><![CDATA[news]]></MsgType>\n" +
                "<ArticleCount>2</ArticleCount>\n" +
                "<Articles>\n" +
                "<item>\n" +
                "<Title><![CDATA[title1]]></Title> \n" +
                "<Description><![CDATA[description1]]></Description>\n" +
                "<PicUrl><![CDATA[picurl]]></PicUrl>\n" +
                "<Url><![CDATA[url]]></Url>\n" +
                "</item>\n" +
                "<item>\n" +
                "<Title><![CDATA[title]]></Title>\n" +
                "<Description><![CDATA[description]]></Description>\n" +
                "<PicUrl><![CDATA[picurl]]></PicUrl>\n" +
                "<Url><![CDATA[url]]></Url>\n" +
                "</item>\n" +
                "</Articles>\n" +
                "</xml>";
        NewsResponse newsRequest = fromXmlToBean(xml, NewsResponse.class);
        String s = fromModelToXml(newsRequest);
        System.out.println(s);

        String xmlMusic = "<xml>\n" +
                "<ToUserName><![CDATA[toUser]]></ToUserName>\n" +
                "<FromUserName><![CDATA[fromUser]]></FromUserName>\n" +
                "<CreateTime>12345678</CreateTime>\n" +
                "<MsgType><![CDATA[music]]></MsgType>\n" +
                "<Music>\n" +
                "<Title><![CDATA[TITLE]]></Title>\n" +
                "<Description><![CDATA[DESCRIPTION]]></Description>\n" +
                "<MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl>\n" +
                "<HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl>\n" +
                "<ThumbMediaId><![CDATA[media_id]]></ThumbMediaId>\n" +
                "</Music>\n" +
                "</xml>";
        BaseResponse musicRequest = fromXmlToBeanWithOutAnnotations(xmlMusic, BaseResponse.class);
        String m = fromModelToXml(musicRequest);
        System.out.println(m);
    }
}
